#include "cast-protect.hpp"
#include <libiberty.h>
#include <ggc.h>
#include <plugin.h>
#include <print-tree.h>
#include <tree.h>
#include <insn-modes.h>
#include <rtl.h>
#include <memmodel.h>
#include <emit-rtl.h>
#include <expr.h>
#include <explow.h>
#include <stor-layout.h>
#include <tree-vrp.h>
#include <stringpool.h>
#include <tree-ssanames.h>
#include <tree-ssa-alias.h>
#include <attribs.h>
#include <cgraph.h>
struct typemode_info {
  tree c_type;
  const char *name;
  machine_mode mode() const {
    return TYPE_MODE(c_type);
  }
};

class cast_function : public nohp_builtin_function {
  const typemode_info from, to;
public:
  cast_function(const typemode_info from, const typemode_info to) : from(from), to(to) {
    name = ggc_strdup(ACONCAT(("__builtin_cast_", from.name, "_", to.name, nullptr)));
  }
  tree type() override {
    return build_function_type_list(to.c_type, from.c_type, NULL_TREE);
  }
  void post_decl() override {
    set_call_expr_flags(decl, ECF_LEAF | ECF_CONST | ECF_NOVOPS | ECF_NOTHROW | ECF_PURE);

    set_builtin_decl(DECL_FUNCTION_CODE(decl), decl, true);
    // TREE_READONLY(decl) = 1;
    // TREE_NOTHROW(decl) = 1;
    // DECL_PURE_P(decl) = 1;
    // DECL_IS_NOVOPS(decl) = 1;
    // DECL_ATTRIBUTES(decl) = make_attribute("leaf", "", DECL_ATTRIBUTES(decl));
    // DECL_ATTRIBUTES(decl) = make_attribute("const", "", DECL_ATTRIBUTES(decl));
  }
  bool gimple_fold(gimple_stmt_iterator *gsi) override{
    gimple *stmt = gsi_stmt(*gsi);
    gcall *call = as_a<gcall*>(stmt);
    // printf("%d %p\n", gimple_clobber_p(stmt), gimple_vdef(stmt));
    // printf("may clobber global: %d\n", stmt_may_clobber_global_p(stmt));
    return false;
    //gimple *stmt = gsi_stmt(*gsi);
    if (gimple_code(stmt) == GIMPLE_CALL){
      auto lhs = gimple_call_lhs(stmt);
      auto rhs = gimple_call_arg(stmt, 0);
      auto new_expr = gimple_build_assign(lhs, fold_build1(VIEW_CONVERT_EXPR, to.c_type, rhs));
      gsi_replace(gsi, new_expr, true);
      return true;
    }
    return false;
  }
  rtx expand(tree exp, rtx target, rtx subtarget, machine_mode mode, int ignore) override {
    if (ignore) return target;
    printf("%d\n", gimple_clobber_p(currently_expanding_gimple_stmt));
    printf("%d\n", currently_expanding_gimple_stmt->has_volatile_ops);
    printf("%p\n", gimple_vdef(currently_expanding_gimple_stmt));
    call_expr_arg_iterator iter;
    auto op = force_reg(from.mode(), expand_expr(first_call_expr_arg(exp, &iter), NULL_RTX, from.mode(), EXPAND_NORMAL));
    return gen_rtx_REG(to.mode(), REGNO(op));
    if (target && REG_P(target)){
      //emit_move_insn(target, gen_rtx_raw_SUBREG(to.mode(), op, 0));
      //return target;
    } else {
      auto intermediate = gen_reg_rtx(to.mode());
      emit_move_insn(intermediate, gen_rtx_raw_SUBREG(to.mode(), op, 0));
      return intermediate;
    }
  }
};

class protect_function : public nohp_builtin_function {
  const typemode_info info;
public:
  protect_function(const typemode_info info): info(info){    
    name = ggc_strdup(ACONCAT(("__builtin_protect_", info.name, NULL)));
  }
  void post_decl() override {
    TREE_READONLY(decl) = 1;
    TREE_NOTHROW(decl) = 1;
  }

  rtx expand(tree exp, rtx target, rtx subtarget, machine_mode mode, int ignore) override {
    if (ignore) return target;
    call_expr_arg_iterator iter;
    auto op = expand_expr(first_call_expr_arg(exp, &iter), NULL_RTX, mode, EXPAND_NORMAL);
    emit_move_insn(target, op);
    return target;
  }
  tree type() override {
    return build_function_type_list(info.c_type, info.c_type, NULL_TREE);
  }
};

static typemode_info vector_types[6];
static typemode_info scalar_types[6];

void sw_64_cast_protect::add_builtins(){
  typemode_info vector_types[] = {
    {V8SI_type_node, "v8si"},
    {unsigned_V8SI_type_node, "uv8si"},
    {V1OI_type_node, "v4di"},
    {unsigned_V1OI_type_node, "uv4di"},
    {V4SF_type_node, "v4sf"},
    {V4DF_type_node, "v4df"},
  };

  typemode_info scalar_types[] = {
    {intSI_type_node, "si"},
    {unsigned_intSI_type_node, "usi"},
    {intDI_type_node, "di"},
    {unsigned_intDI_type_node, "udi"},
    {float_type_node, "sf"},
    {double_type_node, "df"},
  };
  constexpr int nbuiltin_types = 6; //sizeof(vector_types) / sizeof(vector_types[0]);

  for (int i = 0; i < nbuiltin_types; i ++){
    for (int j = 0; j < nbuiltin_types; j ++){
      if (i != j)
        nohp::add_builtin(new cast_function(vector_types[i], vector_types[j]));
    }
    nohp::add_builtin(new cast_function(vector_types[i], scalar_types[i]));
    nohp::add_builtin(new cast_function(scalar_types[i], vector_types[i]));
    nohp::add_builtin(new protect_function(vector_types[i]));
    nohp::add_builtin(new protect_function(scalar_types[i]));
    //nohp::add_builtin(new purestore_function(vector_types[i]));
  }
}
